//! Generic table structure used to represent a value for each position in a
//! chunk as a flat array.
//!
//! If you want to store bits, you probably want `ChunkBitmap`.

use std::mem::{self, MaybeUninit};

use craftdig_math::{SubchunkPos, CHUNK_NUM_BLOCKS};

use super::ChunkTable;

type Inner<T> = [T; CHUNK_NUM_BLOCKS];

#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct ChunkSlotmap<T> {
    entries: Inner<T>,
}

impl<T> ChunkSlotmap<T> {
    pub fn new(entries: Inner<T>) -> Self {
        Self { entries }
    }

    /// Creates a new instance from repeated calls to a function.
    pub fn from_fn(f: impl Fn(SubchunkPos) -> T) -> Self {
        let mut arr: Inner<MaybeUninit<T>> = unsafe { MaybeUninit::uninit().assume_init() };

        for (i, e) in arr.iter_mut().enumerate() {
            let p = SubchunkPos::from_raw(i as u16);
            *e = MaybeUninit::new(f(p));
        }

        let arr = unsafe { MaybeUninit::array_assume_init(arr) };
        Self::new(arr)
    }

    pub fn inner(&self) -> &[T; CHUNK_NUM_BLOCKS] {
        &self.entries
    }

    pub fn into_inner(self) -> [T; CHUNK_NUM_BLOCKS] {
        self.entries
    }

    /// Gets a ref to the value in a position.
    pub fn get(&self, p: SubchunkPos) -> &T {
        &self.entries[p.raw() as usize]
    }

    /// Gets a mut ref to the value in a position.
    pub fn get_mut(&mut self, p: SubchunkPos) -> &mut T {
        &mut self.entries[p.raw() as usize]
    }

    /// Sets a value, returning the old value.
    pub fn set(&mut self, p: SubchunkPos, mut v: T) -> T {
        let vv = self.get_mut(p);
        mem::swap(vv, &mut v);
        v
    }

    /// Iterates over all of the positions in the slotmap.
    pub fn iter_positions(&self) -> impl Iterator<Item = (SubchunkPos, &T)> {
        SubchunkPos::iter().map(|p| (p, &self.entries[p.raw() as usize]))
    }
}

impl<T: Default> ChunkSlotmap<T> {
    pub fn new_default() -> Self {
        let mut arr: Inner<MaybeUninit<T>> = unsafe { MaybeUninit::uninit().assume_init() };

        for e in arr.iter_mut() {
            *e = MaybeUninit::new(T::default());
        }

        let arr = unsafe { MaybeUninit::array_assume_init(arr) };
        Self::new(arr)
    }
}

impl<T: Copy> ChunkSlotmap<T> {
    /// Creates a slotmap with all entries set to this value.
    pub fn with_all(v: T) -> Self {
        Self::new([v; CHUNK_NUM_BLOCKS])
    }
}

impl<T: Clone> ChunkSlotmap<T> {
    /// Creates a slotmap with all entries set to clones of the passed value.
    pub fn with_all_cloned(v: &T) -> Self {
        let mut arr: Inner<MaybeUninit<T>> = unsafe { MaybeUninit::uninit().assume_init() };

        for e in arr.iter_mut() {
            *e = MaybeUninit::new(v.clone());
        }

        let arr = unsafe { MaybeUninit::array_assume_init(arr) };
        Self::new(arr)
    }
}

impl<T: Clone> ChunkTable for ChunkSlotmap<T> {
    type Value = T;

    fn get_clone(&self, pos: SubchunkPos) -> Self::Value {
        self.get(pos).clone()
    }

    fn set_swap(&mut self, pos: SubchunkPos, v: Self::Value) -> Self::Value {
        self.set(pos, v)
    }
}
